package jsen.tool.ddd.adapter.infrastructure.specification.base;

import jsen.tool.ddd.adapter.infrastructure.specification.ISpecification;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Stack;
import java.util.stream.Collectors;

/**
 * Specification define language
 * (((s1 and s2) or s3) and s4) or s5
 *
 * @author jsen
 * @date 2021/10/6
 */
public class Sdl<T> extends CompositeSpecification<T> {
    private final String sdl;
    private final SdlSpecificationSupplier factory;
    private ISpecification<T> trueSpec;

    public Sdl(String sdl, SdlSpecificationSupplier factory) {
        // 格式化语句
        this.sdl = sdl.replaceAll("\\n", " ").replaceAll(" {2,}", " ");
        this.factory = factory;
        // 解析
        parse();
    }

    private void parse() {
        Stack<SdlNode<T>> sdlStack = new Stack<>();

        for (SdlNode<T> node : splitSdl()) {
            if (!node.isRight()) {
                sdlStack.push(node);
                continue;
            }
            reduceSdlNode(getSubNodeStack(sdlStack)).ifPresent(sdlStack::add);
        }
        reduceSdlNode(getSubNodeStack(sdlStack)).ifPresent(sdlStack::add);
        if (sdlStack.size() != 1) {
            throw new RuntimeException("表达式错误" + sdl);
        }

        this.trueSpec = sdlStack.pop().getSpecification();
    }

    private List<SdlNode<T>> splitSdl() {
        List<SdlNode<T>> ops = new ArrayList<>(8);
        StringBuilder word = new StringBuilder();
        for (char c : sdl.toCharArray()) {
            if (c == '(') {
                ops.add(new SdlNode<>(factory, c + ""));
                continue;
            }
            if (c != ')') {
                word.append(c);
            }
            String w = word.toString();
            if (w.equalsIgnoreCase("and ")) {
                ops.add(new SdlNode<>(factory, w.trim()));
                word = new StringBuilder();
            } else if (w.equalsIgnoreCase("or ")) {
                ops.add(new SdlNode<>(factory, w.trim()));
                word = new StringBuilder();
            } else if (w.endsWith(" ") || c == ')') {
                if (w.trim().length() == 0) {
                    continue;
                }
                ops.add(new SdlNode<>(factory, w.trim()));
                word = new StringBuilder();
            }
            if (c == ')') {
                ops.add(new SdlNode<>(factory, c + ""));
            }
        }
        String e = word.toString().trim();
        if (e.length() > 0) {
            ops.add(new SdlNode<>(factory, e));
        }
        return ops;
    }

    private Optional<SdlNode<T>> reduceSdlNode(Stack<SdlNode<T>> sdlNodeStack) {
        if (sdlNodeStack.size() == 1) {
            return Optional.of(sdlNodeStack.pop());
        }
        if (sdlNodeStack.isEmpty()) {
            return Optional.empty();
        }
        // getOp
        List<SdlNode<T>> opNode = sdlNodeStack.stream().filter(n -> n.isAnd()
                || n.isOr()).collect(Collectors.toList());
        if (opNode.isEmpty()) {
            throw new RuntimeException("表达式错误" + sdl);
        }
        if (opNode.stream().allMatch(SdlNode::isAnd)) {
            And<T> and = new And<>();
            while (!sdlNodeStack.isEmpty()) {
                SdlNode<T> n = sdlNodeStack.pop();
                if (!n.isAnd() && !n.isOr()) {
                    and.and(n.getSpecification());
                }
            }
            return Optional.of(new SdlNode<>(factory, and));
        } else {
            Or<T> or = new Or<>();
            while (!sdlNodeStack.isEmpty()) {
                SdlNode<T> n = sdlNodeStack.pop();
                if (!n.isAnd() && !n.isOr()) {
                    or.or(n.getSpecification());
                }
            }
            return Optional.of(new SdlNode<>(factory, or));
        }
    }

    private Stack<SdlNode<T>> getSubNodeStack(Stack<SdlNode<T>> sdlStack) {
        Stack<SdlNode<T>> sub = new Stack<>();
        while (!sdlStack.isEmpty()) {
            SdlNode<T> n = sdlStack.pop();
            if (!n.isLeft()) {
                sub.push(n);
            } else {
                break;
            }
        }
        return sub;
    }

    @Override
    public boolean isSatisfiedBy(SpecContext<T> t) {
        return trueSpec.isSatisfiedBy(t);
    }

}
